/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/irq.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>
#include <linux/dma-direction.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/sched.h>
#include <linux/mm.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <uapi/alga/amd/si/ioctl.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "fops.h"
#include "private.h"

long fops_dce_dp_set(struct pci_dev *dev, void __user *user)
{
	struct dev_drv_data *dd;
	struct si_dce_dp_set dp_set;
	struct dce_db_fb db_fb;
	long r;

	if(copy_from_user(&dp_set, user, sizeof(dp_set)))
		return -EFAULT;

	db_fb.primary = dp_set.primary;
	db_fb.secondary = dp_set.secondary;
	db_fb.pitch = dp_set.pitch;
	strncpy(&db_fb.mode[0], &dp_set.mode[0], sizeof(db_fb.mode));
	strncpy(&db_fb.pixel_fmt[0], &dp_set.pixel_fmt[0], sizeof(db_fb.pixel_fmt));

	dd = pci_get_drvdata(dev);

	r = dce6_sink_mode_set(dd->dce, dp_set.idx, &db_fb);
	if (r != 0)
		return -ENODEV;
	return 0;
}

long fops_dce_dp_dpm(struct pci_dev *dev, u8 __user *user_i)
{
	struct dev_drv_data *dd;
	u8 i;
	long r;

	get_user(i, user_i);

	dd = pci_get_drvdata(dev);

	r = dce6_dp_dpm(dd->dce, i);
	if (r != 0)
		return -ENODEV;
	return 0;
}

/* XXX: run in hard irq context */
static void pf_notify(u8 idx, u32 vblanks_n, struct timespec monotonic_raw_tp,
								void *data)
{
	struct file_private_data *fdata;
	struct fops_evt *fops_evt;
	struct si_evt *si_evt;
	struct si_evt_pf *si_evt_pf;
	struct dev_drv_data *dd;

	fdata = data;

	spin_lock(&fdata->evts_lock);
	fops_evt = kzalloc(sizeof(*fops_evt), GFP_ATOMIC);
	if (!fops_evt) {
		dev_err(&fdata->d->dev, "fops:pf:unable to allocate an event\n");
		goto exit;
	}

	si_evt = &fops_evt->base;
	si_evt_pf = &si_evt->params.pf;

	si_evt->type = SI_EVT_PF;

	si_evt_pf->idx = idx;

	dd = pci_get_drvdata(fdata->d);
	si_evt_pf->vblanks_n = vblanks_n;
	si_evt_pf->monotonic_raw_tp = monotonic_raw_tp;

	list_add_tail(&fops_evt->n, &fdata->evts);
exit:
	spin_unlock(&fdata->evts_lock);
	wake_up(&fdata->evts_wq);
}

long fops_dce_pf(struct pci_dev *dev, u8 __user *user_i, void *data)
{
	struct dev_drv_data *dd;
	u8 i;
	long r;

	get_user(i, user_i);

	dd = pci_get_drvdata(dev);

	r = dce6_pf(dd->dce, i, pf_notify, data);
	if (r == -DCE6_ERR)
		r = -ENODEV;
	return r; /* pass-thru for other return codes */
}

#define EDID_SZ_MAX ((255 + 1) * 128)
long fops_dce_edid(struct pci_dev *dev,
				struct si_dce_edid  __user *user_ioctl_edid)
{
	struct si_dce_edid ioctl_edid;
	void *edid;
	struct dev_drv_data *dd;
	long r;

	r = copy_from_user(&ioctl_edid, user_ioctl_edid, sizeof(ioctl_edid));
	if (r) {
		dev_err(&dev->dev, "dce:ioctl:unable to get user ioctl edid\n");
		return r;
	}

	if (ioctl_edid.edid_sz > EDID_SZ_MAX) {
		dev_err(&dev->dev, "dce:ioctl:edid too big\n");
		return -EINVAL;
	}

	edid = kzalloc(ioctl_edid.edid_sz, GFP_KERNEL);
	if (edid == NULL) {
		dev_err(&dev->dev,
				"dce:ioctl:unable to allocate edid memory\n");
		return -ENOMEM;
	}

	r = copy_from_user(edid, ioctl_edid.edid, ioctl_edid.edid_sz);
	if (r) {
		dev_err(&dev->dev,
			"dce:ioctl:unable to copy edid from userland\n");
		goto err_free_edid;
	}

	dd = pci_get_drvdata(dev);

	r = dce6_edid(dd->dce, ioctl_edid.idx, edid, ioctl_edid.edid_sz);
	if (r != 0) {
		r = -ENODEV;
		goto err_free_edid;
	}
	return 0;

err_free_edid:
	kfree(edid);
	return r;
}
